home *** CD-ROM | disk | FTP | other *** search
- /*
- * Process -- manage i/o with simple subprocesses.
- * Considerably tweaked relative of /NextDeveloper/Examples/Subprocess.
- * M. J. Hawley
- * Copyright (c) MIT Media Laboratory
- * mike@media-lab.mit.edu
- */
- #import "Process.h"
- #import <appkit/nextstd.h>
- #import <appkit/Application.h>
- #import <appkit/Panel.h>
-
- @interface Process(Private)
- - childDidExit;
- - fdHandler:(int)theFd;
- @end
-
- static void
- showError (const char *s, id delegate){ // ensure errors never get lost
- if (delegate && [delegate respondsTo:@selector(processError:)])
- [delegate perform:@selector(processError:) with:(void *)s];
- else if (NXApp) // no delegate, but we're running w/in an App
- NXRunAlertPanel(0, s, 0, 0, 0);
- else
- perror(s);
- }
-
- static void
- fdHandler (int fd, id self) { // DPS handler for output from process
- [self fdHandler:fd];
- }
-
- static void
- getptys(int *master, int *slave){ // attempt to setup the ptys
- #define PTY_TEMPLATE "/dev/pty??"
- #define PTY_LENGTH 11
- char device[PTY_LENGTH];
- char *block, *num;
- char *blockLoc; // specifies the location of block for the device string
- char *numLoc; // specifies the pty name with the digit ptyxD
- char *msLoc; // specifies the master (ptyxx) or slave (ttyxx)
-
- struct sgttyb setp = {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
- struct tchars setc = {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
- struct ltchars sltc = {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
- int lset = (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
- int setd = NTTYDISC;
-
- strcpy(device, PTY_TEMPLATE); // string constants are not writable
- blockLoc = &device[ strlen("/dev/pty") ];
- numLoc = &device[ strlen("/dev/pty?") ];
- msLoc = &device[ strlen("/dev/") ];
- for (block = "pqrs"; *block; block++){
- *blockLoc = *block;
- for (num = "0123456789abcdef"; *num; num++) {
- *numLoc = *num;
- *master = open(device, O_RDWR);
- if (*master >= 0) {
- *msLoc = 't';
- *slave = open(device, O_RDWR);
- if (*slave >= 0) {
- (void) ioctl(*slave, TIOCSETP, (char *)&setp);
- (void) ioctl(*slave, TIOCSETC, (char *)&setc);
- (void) ioctl(*slave, TIOCSETD, (char *)&setd);
- (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
- (void) ioctl(*slave, TIOCLSET, (char *)&lset);
- return;
- }
- }
- } /* hunting through a bank of ptys */
- } /* hunting through blocks of ptys in all the right places */
- *master = -1;
- *slave = -1;
- }
-
- static int
- iwait(fd, timeout)
- long fd;
- unsigned long timeout; /* in seconds */
- /*
- * Wait until 'fd' is ready for reading, or 'timeout'.
- * Return '>=0' when 'fd' is readable, '0' if timeout, '-1' on error.
- * Example: 'iwait(f,0)' polls a file descriptor
- * without blocking and returns true if it's readable;
- * e.g., 'iwait(0,0)' is true when standard input is contains something.
- */
- {
- struct timeval t;
- int readfd = 1<<fd;
-
- t.tv_sec = timeout, t.tv_usec = 0;
- return (int)select(sizeof(int)*8, &readfd, (int *)0, (int *)0, &t);
- }
-
- @implementation Process(Private)
-
- - childDidExit { // cleanup after a child process exits
- if (childPid) {
- if (from) DPSRemoveFD(from);
- close(from);
- if (fpTo) fclose(fpTo);
- if (fpFrom) fclose(fpFrom);
- fpTo = fpFrom = (FILE *)0;
- childPid=0; // specify that child is dead
- if (delegate && [delegate respondsTo:@selector(processDone)])
- [delegate perform:@selector(processDone)];
- }
- return self;
- }
-
- - fdHandler:(int)fd { // DPS handler for output from process
- if (iwait(fd,1)<=0) return self;
- if (((bufferCount=read(fd,buffer,BUFSIZE-1))<0)||(!bufferCount))
- return [self childDidExit];
- buffer[bufferCount] = '\0';
- if (delegate && [delegate respondsTo:action])
- [delegate perform:action with:(void *)&buffer];
- return self;
- }
-
- @end
-
-
- @implementation Process
-
- + new:(char *)process delegate:del {
- self = [Process alloc];
- [self init:process delegate:del andPty:YES andStderr:YES];
- return self;
- }
-
- + new:(char *)process delegate:del andPty:(BOOL)wantsPty andStderr:(BOOL)wantsStderr {
- self = [Process alloc];
- [self init:process delegate:del andPty:wantsPty andStderr:wantsStderr];
- return self;
- }
-
- - init:(char *)process delegate:del {
- return [self init:process delegate:del andPty:NO andStderr:NO];
- }
-
- - init:(char *)process delegate:del andPty:(BOOL)pty andStderr:(BOOL)err {
- // initializes an instance of process and corresponding UNIX process
- int pipeTo[2];
- int pipeFrom[2];
- int tty, numFds, fd; // for temporary use
- int processGroup;
- int pidChild; // needed because childPid does not exist
- // until process is instantiated
-
- [self setAction:@selector(processOutput:)];
- if (pty){
- tty = open("/dev/tty", O_RDWR);
- getptys(&masterPty,&slavePty);
- if (masterPty <= 0 || slavePty <= 0) {
- showError("Error grabbing ptys for subprocess.", del);
- return self;
- }
- // remove the controlling tty if launched from a shell,
- // but not Workspace;
- // so that we have job control over the parent application in shell
- // and so that subprocesses can be restarted in Workspace
- if ((tty<0) && ((tty = open("/dev/tty", 2))>=0)) {
- ioctl(tty, TIOCNOTTY, 0);
- close(tty);
- }
- } else
- if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0){
- showError("Error starting UNIX pipes to process.", del);
- return self;
- }
-
- switch (pidChild = vfork()){
- case -1: // error
- showError("Error starting UNIX vfork of process.", del);
- return self;
-
- case 0: // child
- if (pty) {
- dup2(slavePty, 0);
- dup2(slavePty, 1);
- if (err) dup2(slavePty, 2);
- } else {
- dup2(pipeTo[0], 0);
- dup2(pipeFrom[1], 1);
- if (err) dup2(pipeFrom[1], 2);
- }
-
- numFds = getdtablesize();
- for (fd=3; fd<numFds; fd++)
- close(fd);
-
- processGroup = getpid();
- ioctl(0, TIOCSPGRP, (char *)&processGroup);
- setpgrp (0, processGroup);
-
- // execl(process, 0);
- execl("/bin/sh", "sh", "-c", process, 0);
- perror("vfork (child)"); // should never gets here tho
- exit(1);
-
- default: // parent
- [self setDelegate:del];
- childPid = pidChild;
- if (pty){
- close(slavePty);
- fpTo = fdopen(masterPty, "w");
- from = masterPty;
- fpFrom = fdopen(masterPty, "r");
- } else {
- close(pipeTo[0]);
- close(pipeFrom[1]);
-
- fpTo = fdopen(pipeTo[1], "w");
- from = pipeFrom[0];
- fpFrom = fdopen(pipeFrom[0], "r");
- }
- setbuf(fpTo, NULL);
- setbuf(fpFrom, NULL);
- DPSAddFD(from,(DPSFDProc)fdHandler,(id)self,NX_MODALRESPTHRESHOLD+1);
- // printf("added %d, %d, %d [%d %d]\n",from, self, delegate, pipeTo[1], pipeFrom[0]);
- return self;
- }
- }
-
- - puts:(char *)s {
- fputs(s, fpTo);
- return self;
- }
-
- #define DELAY 15
-
- static void
- _fgets(s,n,f) char *s; int n; FILE *f; {
- int fd = fileno(f);
- while (iwait(fd, DELAY)>0){
- if (read(fd,s,1) != 1){
- *s = '\0';
- return;
- }
- if (*s == 27) *s = '\n';
- if (*s=='\n' || --n <= 0){
- *++s = '\0';
- return ;
- }
- if (*s != '\r') ++s;
- }
- *++s = '\0';
- }
-
- - gets:(char *)s :(int)n {
- *s = '\0';
- _fgets(s,n,fpFrom);
- // fgets(s,n,fpFrom);
- return self;
- }
-
- - (char) getc {
- char c;
- int fd = fileno(fpFrom);
- iwait(fd, DELAY);
- return (read(fd,&c,1)==1)? c : '\0';
- }
-
- - terminate:sender{
- if (childPid){
- kill(childPid+1, SIGTERM);
- [self childDidExit];
- }
- return self;
- }
-
- - setDelegate:anObject {
- delegate = anObject;
- return self;
- }
-
- - delegate {
- return delegate;
- }
-
- - setAction:(SEL)theAction {
- action = theAction;
- return self;
- }
- @end
-